'use strict';

const Promise = require('bluebird');
const path = require('path');
const { createBundleRenderer } = require('vue-server-renderer');
const LRU = require('lru-cache');

const env = require('gitter-web-env');
const logger = env.logger;

// Default to production unless we know for sure we are in dev
const IS_PRODUCTION = process.env.NODE_ENV !== 'dev';

function createRenderer(bundle, options) {
  // https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer
  return createBundleRenderer(
    bundle,
    Object.assign(options, {
      // for component caching
      cache: LRU({
        max: 1000,
        maxAge: 1000 * 60 * 15
      }),
      // this is only needed when vue-server-renderer is npm-linked
      basedir: path.resolve(__dirname, '../../../output/assets/js/'),
      // recommended for performance
      runInNewContext: false
    })
  );
}

function setupDevServer(bundleUpdatedCallback) {
  const webpack = require('webpack'); // eslint-disable-line node/no-unpublished-require
  const MFS = require('memory-fs'); // eslint-disable-line node/no-unpublished-require

  const clientConfig = require('../../../public/js/webpack.config');
  const serverConfig = require('../../../public/js/webpack.server.config');

  const readFile = (fs, file) => {
    try {
      return fs.readFileSync(path.join(clientConfig.output.path, file), 'utf-8');
    } catch (err) {
      // We swallow the error because the next build could be fien
      logger.error(err);
    }
  };

  const initialCompileDonePromise = new Promise(resolve => {
    // watch and update server renderer
    const serverCompiler = webpack(serverConfig);
    const mfs = new MFS();
    serverCompiler.outputFileSystem = mfs;
    serverCompiler.watch({}, (err, stats) => {
      if (err) {
        logger.error(err);
        throw err;
      }
      stats = stats.toJson();
      if (stats.errors.length) {
        logger.error(
          `Errors while compiling server webpack config (${stats.errors.length})`,
          stats.errors
        );
        return;
      }

      // Read the bundle generated by vue-ssr-webpack-plugin
      const bundle = JSON.parse(readFile(mfs, 'vue-ssr-server-bundle.json'));

      bundleUpdatedCallback(bundle);
      resolve();
    });
  });

  return initialCompileDonePromise;
}

// https://ssr.vuejs.org/api/#template
// https://ssr.vuejs.org/guide/build-config.html#manual-asset-injection
const rendererTemplate = (result, context) => {
  const stateHtml = context.renderState();

  return `${stateHtml}${context.styles}${result}`;
};

let renderer;
let readyPromise = Promise.resolve();
if (IS_PRODUCTION) {
  // eslint-disable-next-line node/no-unpublished-require, node/no-missing-require
  const bundle = require('../../../output/assets/js/vue-ssr-server-bundle.json');
  renderer = createRenderer(bundle, {
    template: rendererTemplate
  });
} else {
  // In development: setup the dev server with watch and hot-reload,
  // and create a new renderer on bundle update.
  readyPromise = setupDevServer(bundle => {
    renderer = createRenderer(bundle, {
      template: rendererTemplate
    });
  });
}

async function renderToString(...args) {
  await readyPromise;

  return renderer.renderToString(...args);
}

module.exports = renderToString;
